package com.arcticcoding.controlphone;

import java.util.List;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.arcticcoding.controlphone.Network.MovementMessage;
import com.arcticcoding.dto.ImageFrame;
import com.arcticcoding.exceptions.SensorException;
import com.arcticcoding.telemetrie.TelemetrieClient;
import com.arcticcoding.telemetrie.TelemetrieClientListener;
import com.arcticcoding.telemetrie.TelemetrieClient.TeleClientBinder;
import com.esotericsoftware.kryonet.Client;
import com.esotericsoftware.kryonet.Connection;

/**
 * Activity, in der die Steuerbefehle fuer Marvin generiert werden und ueber
 * Netzwerk weitergesendet werden
 * 
 * @author Daniel Horbach, Steffen Zuber
 * 
 */
public class ControlActivity extends Activity implements SensorEventListener {

	private static final double MAX_ANGLE = 45;
	private static final double MAX_STEERING_ANGLE = 60;
	private static final int NST_P = 9;
	private static final int NST_N = -9;
	private static final String SENSOR_ERROR = "Es ist ein Fehler beim initialisieren der Sensoren aufgetreten!";
	private static final String TAG = "Sensor";

	private SensorManager mSensorManager;

	private float[] mAcceleration = new float[3];
	private float[] mMagenticField = new float[3];

	private float[] mOrientation = new float[3];
	private float[] mRotationMatrix = new float[9];

	private double[] mLastValues = new double[3];
	private boolean moveActive = false;
	private boolean cameraActive = false;

	// Elemente fuer das finale Layout
	private ImageButton controlButton;
	private ImageButton cameraButton;
	private ImageButton ledButton;
	private ImageButton camLeftButton;
	private ImageButton camRightButton;
	private TextView battery;
	private ImageView cameraView;
	// ----------------------------------

	private boolean cameraOn = false;
	private boolean ledOn = false;

	private static final int TCP_PORT = 2045;
	private static final int UDP_PORT = 2046;
	private String address;
	private TelemetrieClient teleClient;
	private Intent intent;

	private int oldAngle = 0;

	private Client client;
	int geschwindigkeitAlt = 0;
	int geschwindikeitNeu = 0;
	int winkelAlt = 0;
	int winkelNeu = 0;

	private ServiceConnection teleClientConn = new ServiceConnection() {

		@Override
		public void onServiceConnected(ComponentName arg0, IBinder service) {
			TeleClientBinder binder = (TeleClientBinder) service;
			teleClient = binder.getService();
			Log.d("teleClient", teleClient.toString());
			address = LaunchActivity.ip;
			Log.d("teleClient", address);
			teleClient.connect(address, TCP_PORT, UDP_PORT);
			teleClient.addClientListener(new MyClientListener());
			Log.d("TeleConn", "Service registered");
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			Log.d("TeleConn", "Service unregistered");
		}

	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_control_final);

		intent = new Intent(this, TelemetrieClient.class);

		client = LaunchActivity.client;

		mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		try {
			registerListeners();
		} catch (SensorException e) {
			Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show();
			e.printStackTrace();
		}

		controlButton = (ImageButton) findViewById(R.id.buttonMove);
		cameraButton = (ImageButton) findViewById(R.id.buttonCamera);
		ledButton = (ImageButton) findViewById(R.id.buttonLed);
		camLeftButton = (ImageButton) findViewById(R.id.buttonCamL);
		camRightButton = (ImageButton) findViewById(R.id.buttonCamR);
		battery = (TextView) findViewById(R.id.textViewBattery);
		cameraView = (ImageView) findViewById(R.id.imageViewCamera);

		controlButton.setAlpha(100);
		cameraButton.setAlpha(100);
		ledButton.setAlpha(100);
		camLeftButton.setAlpha(100);
		camRightButton.setAlpha(100);

		controlButton.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				if (event.getAction() != MotionEvent.ACTION_UP) {
					lockPosition();
					return true;
				} else {
					releasePosition();
					return false;
				}
			}
		});

		camLeftButton.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				// TODO Auto-generated method stub
				if (event.getAction() != MotionEvent.ACTION_UP) {
					String befehl = "marvin;c;turnL";
					sendMessage(befehl);
					return true;
				} else {
					String befehl = "marvin;c;stop";
					sendMessage(befehl);
					return false;
				}
			}
		});

		camRightButton.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				// TODO Auto-generated method stub
				if (event.getAction() != MotionEvent.ACTION_UP) {
					String befehl = "marvin;c;turnR";
					sendMessage(befehl);
					return true;
				} else {
					String befehl = "marvin;c;stop";
					sendMessage(befehl);
					return false;
				}
			}
		});

	}

	/**
	 * Die Methode gibt an, was passiert, wenn man auf den Led- bzw. den
	 * Camera-Button klickt
	 * 
	 * @param view
	 *            Button, auf den geklickt wurde
	 */
	public void clickButton(View view) {
		int id = view.getId();
		if (id == R.id.buttonCamera) {
			if (cameraOn) {
				teleClient.sendStopStreaming();
				cameraButton.setImageResource(R.drawable.camera_red);
				cameraButton.setAlpha(100);
				cameraOn = false;
			} else {
				teleClient.sendStartStreaming();
				cameraButton.setImageResource(R.drawable.camera_green);
				cameraButton.setAlpha(100);
				cameraOn = true;
			}
		} else if (id == R.id.buttonLed) {
			if (ledOn) {
				teleClient.sendLedOff();
				ledButton.setImageResource(R.drawable.led_red);
				ledButton.setAlpha(100);
				ledOn = false;
			} else {
				teleClient.sendLedOn();
				ledButton.setImageResource(R.drawable.led_green);
				ledButton.setAlpha(100);
				ledOn = true;
			}
		}
	}

	/**
	 * Die Methode speichert die Position, in der das Handy gehalten wird
	 */
	private void lockPosition() {
		moveActive = true;
	}

	private void releasePosition() {
		moveActive = false;
		sendMessage("marvin;v;-0");
		sendMessage("marvin;w;-0");
	}

	private void registerListeners() throws SensorException {
		try {
			mSensorManager
					.registerListener(this, mSensorManager
							.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
							SensorManager.SENSOR_DELAY_NORMAL);
			mSensorManager.registerListener(this,
					mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
					SensorManager.SENSOR_DELAY_NORMAL);
		} catch (Exception e) {
			new SensorException(SENSOR_ERROR);
			Log.wtf(TAG, e);
			e.printStackTrace();
		}
	}

	private void unregisterListeners() {
		mSensorManager.unregisterListener(this);
	}

	@Override
	protected void onDestroy() {
		unregisterListeners();
		super.onDestroy();
	}

	@Override
	protected void onPause() {
		unregisterListeners();
		super.onPause();
	}

	@Override
	protected void onResume() {
		try {
			registerListeners();
		} catch (SensorException e) {
			Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show();
			e.printStackTrace();
		}
		super.onResume();
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {

	}

	@Override
	public void onSensorChanged(SensorEvent event) {
		switch (event.sensor.getType()) {
		case Sensor.TYPE_ACCELEROMETER:
			mAcceleration = event.values.clone();
			break;
		case Sensor.TYPE_MAGNETIC_FIELD:
			mMagenticField = event.values.clone();
			break;
		}
		computeOrientation();
	}

	/**
	 * Berechnet die Gradzahlen, um die das Handy an den Achsen gedreht ist.
	 */
	private void computeOrientation() {
		if (SensorManager.getRotationMatrix(mRotationMatrix, null,
				mAcceleration, mMagenticField)) {
			SensorManager.getOrientation(mRotationMatrix, mOrientation);

			// Math.round eingefuegt
			double azimuthDeg = Math.round(Math.toDegrees(mOrientation[0]));
			double pitchDeg = Math.round(Math.toDegrees(mOrientation[1]));
			double rollDeg = Math.round(Math.toDegrees(mOrientation[2]));

			if (!moveActive && !cameraActive) {
				mLastValues[0] = azimuthDeg;
				mLastValues[1] = pitchDeg;
				mLastValues[2] = rollDeg;
			}

			computeRelativeValues(mLastValues, azimuthDeg, pitchDeg, rollDeg); // meine
																				// Funktion,
																				// um
																				// die
																				// Werte
																				// anzupassen
		}
	}

	/**
	 * Berechnet die Werte bezogen zum gesetzten Fixpunkt
	 * 
	 * @param fixPoint
	 *            - Array mit dem Fixpunkt
	 * @param azimuth
	 *            - Azimuth-Wert des Sensors
	 * @param pitch
	 *            - Pitch-Wert des Sensors
	 * @param roll
	 *            - Roll-Wert des Sensors
	 */
	private void computeRelativeValues(double[] fixPoint, double azimuth,
			double pitch, double roll) {
		double relativeAzimuth;
		double relativePitch;
		double relativeRoll;

		if (azimuth <= fixPoint[0]) {
			relativeAzimuth = -(Math.abs(azimuth) - Math.abs(fixPoint[0]));
		} else if (azimuth > fixPoint[0] && azimuth <= 0) {
			relativeAzimuth = Math.abs(fixPoint[0]) - Math.abs(azimuth);
		} else {
			relativeAzimuth = Math.abs(fixPoint[0]) + azimuth;
		}

		if (pitch <= fixPoint[1]) {
			relativePitch = -(Math.abs(pitch) - Math.abs(fixPoint[1]));
		} else if (pitch > fixPoint[1] && pitch <= 0) {
			relativePitch = Math.abs(fixPoint[1]) - Math.abs(pitch);
		} else {
			relativePitch = Math.abs(fixPoint[1]) + pitch;
		}

		if (roll <= fixPoint[2]) {
			relativeRoll = -(Math.abs(roll) - Math.abs(fixPoint[2]));
		} else if (roll > fixPoint[2] && roll <= 0) {
			relativeRoll = Math.abs(fixPoint[2]) - Math.abs(roll);
		} else {
			relativeRoll = Math.abs(fixPoint[2]) + roll;
		}

		if (moveActive) {
			calculateSpeed(relativeRoll);
			calculateAngles(relativePitch);
		}
	}

	/**
	 * Einfache Testfunktion um eine Anzeige zu bekommen, so nicht final
	 * 
	 * @param angle
	 *            Neigungswinkel nach vorne im Landscape-Modus
	 */
	public void calculateSpeed(double angle) {
		String befehl = null;
		if (angle > 1 && angle <= 10) {
			geschwindikeitNeu = 25;
			if (geschwindigkeitAlt != geschwindikeitNeu) {
				befehl = "marvin;v;+25";
				sendMessage(befehl);
				geschwindigkeitAlt = geschwindikeitNeu;
			}
		}
		if (angle > 10 && angle <= 20) {
			geschwindikeitNeu = 50;
			if (geschwindigkeitAlt != geschwindikeitNeu) {
				befehl = "marvin;v;+50";
				sendMessage(befehl);
				geschwindigkeitAlt = geschwindikeitNeu;
			}
		}
		if (angle > 20 && angle <= 30) {
			geschwindikeitNeu = 75;
			if (geschwindigkeitAlt != geschwindikeitNeu) {
				befehl = "marvin;v;+75";
				sendMessage(befehl);
				geschwindigkeitAlt = geschwindikeitNeu;
			}
		}
		if (angle > 30 && angle <= 40) {
			geschwindikeitNeu = 100;
			if (geschwindigkeitAlt != geschwindikeitNeu) {
				befehl = "marvin;v;+100";
				sendMessage(befehl);
				geschwindigkeitAlt = geschwindikeitNeu;
			}
		}
		if (angle < -5 && angle >= -15) {
			geschwindikeitNeu = -25;
			if (geschwindigkeitAlt != geschwindikeitNeu) {
				befehl = "marvin;v;-25";
				sendMessage(befehl);
				geschwindigkeitAlt = geschwindikeitNeu;
			}
		}
		if (angle < -15 && angle <= -25) {
			geschwindikeitNeu = -50;
			if (geschwindigkeitAlt != geschwindikeitNeu) {
				befehl = "marvin;v;-50";
				sendMessage(befehl);
				geschwindigkeitAlt = geschwindikeitNeu;
			}
		}
		if (angle < 1 && angle > -5) {
			geschwindikeitNeu = 0;
			if (geschwindigkeitAlt != geschwindikeitNeu) {
				befehl = "marvin;v;-0";
				sendMessage(befehl);
				geschwindigkeitAlt = geschwindikeitNeu;
			}
		}
	}

	/**
	 * Methode die den Steuerungswinkel berechnet
	 * 
	 * @param angle
	 *            Winkel des Sensors im Telefon
	 */
	public void calculateAngles(double angle) {
		int result = 0;
		String befehl = null;
		if (angle < NST_P && angle > -NST_P) { // Nullstelung von -9 bis 9
			winkelNeu = result;
			if (winkelAlt != winkelNeu) {

				befehl = "marvin;w;-" + result;

				sendMessage(befehl);
				winkelAlt = winkelNeu;
			}
		} else {

			if (angle < 30 || angle > -30)
				result = (int) (MAX_STEERING_ANGLE / 2 / MAX_ANGLE * angle);
			else
				result = (int) (MAX_STEERING_ANGLE / MAX_ANGLE * angle);

			if (angle < -MAX_ANGLE)
				result = (int) -MAX_STEERING_ANGLE;
			if (angle > MAX_ANGLE)
				result = (int) MAX_STEERING_ANGLE;

			if (Math.abs(Math.abs(oldAngle) - Math.abs(result)) > 6
					|| result == MAX_STEERING_ANGLE
					|| result == -MAX_STEERING_ANGLE) {
				oldAngle = result;
				winkelNeu = result;
				if (winkelAlt != winkelNeu) {

					befehl = "marvin;w;" + result;

					sendMessage(befehl);
					winkelAlt = winkelNeu;
				}
			}
		}
	}

	/**
	 * Die Methode startet einen neuen Thread und sendet den uebergebenen String
	 * als MovementMessage an den Server.
	 * 
	 * @param s
	 *            String, der gesendet werden soll
	 */
	private void sendMessage(final String s) {
		new Thread() {
			public void run() {
				MovementMessage movementMessage = new MovementMessage();
				movementMessage.text = s;
				client.sendTCP(movementMessage);
			}
		}.start();
	}

	private class MyClientListener extends TelemetrieClientListener {

		@Override
		public void connected(Connection conn) {
			// TODO
			runOnUiThread(new Runnable() {

				@Override
				public void run() {
					Toast.makeText(ControlActivity.this,
							"Connected to Telemetrie-Server!",
							Toast.LENGTH_LONG).show();
				}
			});
			super.connected(conn);
		}

		@Override
		public void disconnected(Connection conn) {
			// TODO
			runOnUiThread(new Runnable() {

				@Override
				public void run() {
					Toast.makeText(ControlActivity.this,
							"Disconnected from Telemetrie-Server!",
							Toast.LENGTH_LONG).show();
				}
			});
			super.disconnected(conn);
		}

		@Override
		public void cameraImageReceived(ImageFrame frame) {
			final Bitmap image;

			image = BitmapFactory.decodeByteArray(frame.imageData, 0,
					frame.imageData.length);
			runOnUiThread(new Runnable() {

				@Override
				public void run() {
					cameraView.setImageBitmap(image);
				}
			});
		}

		@Override
		public void cameraSupportedFrameRatesReceived(List<Integer[]> frameRates) {
			// TODO Auto-generated method stub
		}

		@Override
		public void geoLocationReceived(com.arcticcoding.dto.Location location) {
			// TODO Auto-generated method stub
		}

	}

	@Override
	protected void onStart() {
		// TODO Auto-generated method stub
		bindService(intent, teleClientConn, Context.BIND_AUTO_CREATE);
		super.onStart();
	}

	@Override
	protected void onStop() {
		// TODO Auto-generated method stub
		unbindService(teleClientConn);
		super.onStop();
	}

}
